Taeseong Blog

V8 엔진의 동작원리 "조금 더" 알아보기

2025-04-16

V8 엔진JavaScript

JavaScript 실행의 숨겨진 여행: 브라우저에서 코드가 살아나기까지

JavaScript는 처음에는 간단한 웹 스크립팅 언어로 시작했지만, 오늘날에는 명령형, 함수형, 프로토타입 기반 객체지향 프로그래밍을 모두 지원하는 다재다능한 언어로 발전했습니다. 이 글에서는 브라우저에서 JavaScript 코드가 실행되기까지의 여정을 살펴보겠습니다.

브라우저와 JavaScript 엔진의 관계

브라우저는 본질적으로 웹 문서를 해석하고 표시하는 큰 해석기입니다. 그 중에서 'JavaScript 엔진'은 우리가 HTML 파일에 <script> 태그로 작성한 코드를 실행하는 핵심 부분입니다.

브라우저의 렌더링 엔진이 HTML을 파싱하다가 <script> 태그를 만나면, DOM 생성을 일시 중단하고 JavaScript 파일을 로드합니다. 그리고 JavaScript 엔진에게 제어권을 넘겨 코드를 실행합니다.

V8 엔진: JavaScript의 심장부

구글의 V8 엔진은 크롬, 노드JS 등에서 사용되는 강력한 JavaScript 엔진입니다. C++로 작성되었으며, 특히 JIT(Just In Time) 컴파일러를 활용해 다른 엔진들과 차별화됩니다.

V8 엔진의 작동 원리

  1. 파서(Parser) 단계:

    • 렉시컬 분석: 코드를 토큰으로 분해
    • 구문 분석: 토큰의 구조 파악
    • AST 생성: 추상 구문 트리(Abstract Syntax Tree) 생성 (대부분의 프로그래밍 언어는 AST를 이용해서 상위 수준의 코드 표현을 하위 수준의 표현으로 변환한다.)
  2. Ignition: AST를 바이트코드로 변환하여 실행

  3. TurboFan: 자주 사용되는 코드를 최적화된 기계어 코드로 컴파일

재미있는 사실: V8은 8기통 엔진에서 이름을 따왔습니다. Ignition(점화기)은 엔진 시동을, TurboFan은 뜨거워진 부분을 식히는 팬을 의미합니다!

JavaScript 엔진의 내부 구조

JavaScript 런타임 환경은 크게 두 부분으로 나뉩니다

1. 메모리 힙(Memory Heap)

변수와 데이터를 저장하는 공간입니다. 이곳에서는 가비지 컬렉션(GC)이 더 이상 사용되지 않는 메모리를 회수합니다.

2. 콜 스택(Call Stack)

코드 실행 순서를 기록하고 관리하는 공간입니다. LIFO(Last In, First Out) 방식으로 동작합니다.

function add(a, b) {
    return a + b;
}

function average(a, b) {
    return add(a, b) / 2;
}

console.log(average(10, 20));

위 코드를 실행하면:

  1. console.log가 스택에 쌓임
  2. average 함수가 그 위에 쌓임
  3. add 함수가 가장 위에 쌓임
  4. 역순으로 실행: addaverageconsole.log

만약 콜 스택이 넘치면? 바로 '스택 오버플로우(Stack Overflow)' 오류가 발생합니다.

JavaScript의 한계와 해결책: 비동기 처리

JavaScript는 기본적으로 싱글 스레드입니다. 즉, 한 번에 하나의 작업만 처리할 수 있습니다. 만약 10초 걸리는 작업이 있다면, 다른 모든 작업은 기다려야 합니다.

이 문제를 해결하기 위해 등장한 것이 Event Loop, Callback Queue, 그리고 Web APIs입니다.

Event Loop

콜백 함수들을 관리하고, 콜 스택이 비었을 때 콜백 큐에서 함수를 콜 스택으로 옮겨주는 역할을 합니다. 이 과정을 '틱(tick)'이라고 합니다.

Callback Queue

비동기 작업이 완료된 후 실행될 콜백 함수들이 대기하는 공간입니다. FIFO(First In First Out) 방식으로 작동합니다.

Web APIs

브라우저에서 제공하는 API로, DOM 이벤트, Ajax, setTimeout 등의 비동기 작업을 처리합니다.

실제 동작 예시

console.log("시작");

setTimeout(function() {
    console.log("3초 후 실행");
}, 3000);

console.log("종료");

이 코드는 다음과 같이 실행됩니다:

  1. console.log("시작") → 콜 스택에 들어가서 즉시 실행
  2. setTimeout → 콜 스택에 들어갔다가 Web API로 이동, 타이머 시작
  3. console.log("종료") → 콜 스택에 들어가서 즉시 실행
  4. 3초 후, 타이머 완료 → 콜백 함수가 콜백 큐로 이동
  5. Event Loop가 콜 스택이 비어있음을 확인 → 콜백 함수를 콜 스택으로 이동
  6. console.log("3초 후 실행") 실행

결과는 "시작", "종료", "3초 후 실행" 순으로 출력됩니다.

정리

브라우저에서 JavaScript가 실행되기까지의 과정은 복잡하지만 정교합니다. HTML 파싱 → JavaScript 엔진(V8) → 코드 파싱 → AST 생성 → 바이트코드 변환 및 실행 → 비동기 처리(Event Loop, Callback Queue, Web APIs)의 과정을 거쳐 우리가 원하는 동작을 수행합니다.

JavaScript가 싱글 스레드임에도 불구하고 비동기 처리를 통해 복잡한 웹 애플리케이션을 구현할 수 있는 것은 이러한 정교한 메커니즘 덕분입니다. 이런 내부 동작 원리를 이해하면 더 효율적인 코드를 작성할 수 있고, 성능 향상에도 도움이 됩니다.